/**
 * Copyright (C) 2010 DustedPixels.com
 */
package com.dustedpixels.cirt.model.cpu.z80;

import com.dustedpixels.cirt.model.core.Circuit;
import com.dustedpixels.cirt.model.core.In;
import com.dustedpixels.cirt.model.core.Out;
import com.dustedpixels.cirt.model.core.Pins;
import com.dustedpixels.cirt.model.core.Script;

/**
 * Z80 instruction decoder.
 *
 * @author Michal Pociecha-Los (micapolos@gmail.com)
 */
@Circuit
public final class InstructionDecoder2 {
  // An opcode
  @In @Pins(8) public int opcode;
  
  // A cycle within given opcode
  @In @Pins(4) public int cycle;
  
  // true = enable index addressing mode,where HL becomes IX/IY + d
  // Dependent out: iyIndex
  @In public boolean indexPrefix;
  
  // Valid only of indexPrefix = true;
  // false = use IX index, true = use IY index 
  @In public boolean iyIndex;
  
  // true = enable extended instruction set
  // Dependent outs: edInstructionSet
  @In public boolean extendedInstructionSet;
  
  // Valid only of extendedInstructionSet = true;
  // false = CB extended instruction set, true = ED extended instruction set
  @In public boolean edInstructionSet;
  
  // Requests the control to be given back to machine cycle controller.
  // It requests either data cycle, or fetch cycle.
  @Out public boolean machineCycleRequest;
  
  // false = M1 cycle, true = data cycle
  @Out public boolean dataRequest;
  
  // Valid only if dataRequest = true
  // false = data read request, true = data write request
  @Out public boolean writeRequest;
  
  // Valid only if dataRequest = true
  // false = memory request, true = IO request
  @Out public boolean ioRequest;
  
  // Request the content of an 8-bit register to be put on the data bus
  @Out public boolean reg8Out;
  
  // Requested register (B,C,D,E,H,L,W,Z)
  @Out public int reg8OutSelect;
  
  // Requests the content of the data bus to be stored in an 8-bit register
  @Out public boolean reg8In;
  
  // Requested register (B,C,D,E,H,L,W,Z)
  @Out public int reg8InSelect;
  
  // Request the content of an accumulator to be put on the data bus
  @Out public boolean accOut;
  
  // Request the content of the data bus to be stored in ACC
  @Out public boolean accIn;
  
  // Request the content of a 16-bit register to be put on the address bus
  @Out public boolean reg16Out;
  
  // Requested register (BC, DE, HL, WZ)
  @Out public int reg16OutSelect;
  
  // HALT request
  @Out public boolean halt;
  
  // Current instruction cycle
  private int instructionCycle;
  
  // Current opcode, and its masked-out pieces
  private int currentOpcode;
  private int currentOpcode012;
  private int currentOpcode345;

  @Script
  public void update() {
    if (newOpcode) {
      // Store new opcode and reset instruction counter
      currentOpcode = opcode;
      instructionCycle = 0;
    }

    // Reset state
    fetchRequest = false;
    
    if (dataRequest) {
      dataRequest = false;
      writeRequest = false;
      ioRequest = false;
    }
    
    if (reg8In) {
      reg8In = false;
      reg8InSelect = 0;
    }
    
    if (reg8Out) {
      reg8Out = false;
      reg8OutSelect = 0;
    }
    
    if (reg16Out) {
      reg16Out = false;
      reg16OutSelect = 0;
    }
    
    accIn = false;
    accOut = false;
    
    halt = false;

    if (!extendedInstructionSet) {
      // Standard instruction set
      switch (currentOpcode) {
        case 0x00:
          fetchRequest = true;
          break;
          
        case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47:
        case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F:
        case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57:
        case 0x58: case 0x59: case 0x5A: case 0x5B: case 0x5C: case 0x5D: case 0x5E: case 0x5F:
        case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67:
        case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F:
        case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77:
        case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F:
          switch (instructionCycle) {
            case 0:
              currentOpcode012 = opcode & 0x07;
              currentOpcode345 = (opcode >>> 3) & 0x07;
              
              boolean reg8OrAccOut = false;
              boolean reg8OrAccIn = false;
              
              if (currentOpcode012 == 0x06) {
                if (currentOpcode345 == 0x06) {   
                  // HALT
                  halt = true;
                  fetchRequest = true;
                } else {
                  // LD r,(HL)
                  dataRequest = true;
                  reg16Out = true;
                  reg16OutSelect = 0x02; // HL
                  instructionCycle++;
                }
              } else {
                reg8OrAccOut = true;
                
                if (currentOpcode345 == 0x06) {
                  // LD (HL),r
                  dataRequest = true;
                  writeRequest = true;
                  ioRequest = false;
                  reg16Out = true;
                  reg16OutSelect = 0x02; // HL
                  instructionCycle++;
                } else {
                  reg8OrAccIn = true;
                  fetchRequest = true;
                }
              }
              
              if (reg8OrAccOut) {
                // LD r,r'
                if (currentOpcode012 == 0x07) {
                  accOut = true;
                } else {
                  reg8Out = true;
                  reg8OutSelect = currentOpcode012;
                }
              }
  
              if (reg8OrAccIn) {
                if (currentOpcode345 == 0x07) {
                  accIn = true;
                } else {
                  reg8In = true;
                  reg8InSelect = currentOpcode345;
                }
              }
              break;
            case 1:
              dataRequest = false;
              reg16Out = false;
              if (currentOpcode345 == 0x07) {
                accIn = true;
              } else if (currentOpcode345 != 0x06) {
                reg8In = true;
                reg8InSelect = currentOpcode345;
              }
              fetchRequest = true;
              break;
          }
          
          break;
      }
    }
  }
}
